package wiki.xsx.core.bytecode.transformer;

import javassist.CtBehavior;
import javassist.CtClass;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LineNumberAttribute;
import lombok.SneakyThrows;
import wiki.xsx.core.bytecode.BytecodeUtil;
import wiki.xsx.core.bytecode.enums.ExecuteType;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * 抽象方法转换器
 *
 * @author xsx
 * @date 2023/3/2
 * @since 1.8
 * <p>
 * Copyright (c) 2023 xsx All Rights Reserved.
 * easy-bytecode is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 * http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * </p>
 */
public abstract class AbstractMethodTransformer extends AbstractTransformer {

    /**
     * 执行类型
     */
    protected ExecuteType executeType;
    /**
     * 参数类型
     */
    protected List<Class<?>> parameterTypes;
    /**
     * 行号
     */
    protected Integer lineNum;
    /**
     * 源码数组
     */
    protected String[] sources;

    /**
     * 有参构造
     *
     * @param isWrite        是否写入
     * @param fileDir        文件目录
     * @param executeType    执行类型
     * @param className      类名称
     * @param methodName     方法名称
     * @param parameterTypes 参数类型
     * @param lineNum        源码行号
     * @param sources        源码
     */
    public AbstractMethodTransformer(
            Boolean isWrite,
            String fileDir,
            ExecuteType executeType,
            String className,
            String methodName,
            List<Class<?>> parameterTypes,
            Integer lineNum,
            String... sources
    ) {
        super(isWrite, fileDir, className, methodName);
        this.executeType = executeType;
        this.parameterTypes = Optional.ofNullable(parameterTypes).orElse(new ArrayList<>(0));
        this.lineNum = lineNum;
        this.sources = sources;
    }

    /**
     * 处理
     *
     * @param ctClass    ct类
     * @param ctBehavior ct行为
     */
    @SneakyThrows
    protected void process(CtClass ctClass, CtBehavior ctBehavior) {
        switch (this.executeType) {
            // 插入
            case METHOD_INSERT_AT: {
                ctBehavior.insertAt(this.lineNum, this.sources[0]);
                break;
            }
            case METHOD_INSERT_BEFORE: {
                ctBehavior.insertBefore(this.sources[0]);
                break;
            }
            case METHOD_INSERT_AFTER: {
                ctBehavior.insertAfter(this.sources[0]);
                break;
            }
            case METHOD_INSERT_AFTER_FINALLY: {
                ctBehavior.insertAfter(this.sources[0], true);
                break;
            }
            case METHOD_INSERT_AROUND: {
                ctBehavior.insertBefore(this.sources[0]);
                ctBehavior.insertAfter(this.sources[1], false, true);
                break;
            }
            case METHOD_INSERT_AROUND_FINALLY: {
                ctBehavior.insertBefore(this.sources[0]);
                ctBehavior.insertAfter(this.sources[1], true);
                break;
            }

            // 修改
            case METHOD_REPLACE_AT: {
                this.updateAt(ctBehavior);
                break;
            }
            case METHOD_REPLACE_ALL: {
                ctBehavior.setBody(this.sources[0]);
                break;
            }

            // 添加异常捕获
            case METHOD_ADD_CATCH: {
                ctBehavior.addCatch(this.sources[0], BytecodeUtil.getClassPool().get(this.sources[1]));
                break;
            }
        }
    }

    /**
     * 指定位置更新
     *
     * @param ctBehavior ct行为
     */
    @SneakyThrows
    private void updateAt(CtBehavior ctBehavior) {
        CodeAttribute codeAttribute = ctBehavior.getMethodInfo().getCodeAttribute();
        LineNumberAttribute lineNumberAttribute = (LineNumberAttribute) codeAttribute.getAttribute(LineNumberAttribute.tag);
        int begin = lineNumberAttribute.toStartPc(this.lineNum);
        int end = lineNumberAttribute.toStartPc(this.lineNum + 1);
        byte[] code = codeAttribute.getCode();
        for (int i = begin; i < end; i++) {
            code[i] = CodeAttribute.NOP;
        }
        ctBehavior.insertAt(this.lineNum, this.sources[0]);
    }
}
